#pragma once

#include "indicator_common.h"

namespace mdtx::online::indicator
{

    // basic MA

    class sma
    {
    private:
        boost::circular_buffer<double> xbuf;
        mdtx::online::updater::sma_updater<MDTX_INDICATOR_KAHAN> mx;

        double insert(double x)
        {
            if (xbuf.full())
            {
                mx.roll(x, xbuf.back());
            }
            else
            {
                mx.insert(x);
            }
            xbuf.push_front(x);
            return mx.m;
        }

    public:
        explicit sma(int period)
            : xbuf(period),
              mx()
        {
        }

        template <class InputIt, class OutputIt>
        void operator()(InputIt first, InputIt last, OutputIt output)
        {
            while (first != last)
            {
                *output++ = insert(*first++);
            }
        }

        void operator()(double input, double &output)
        {
            output = insert(input);
        }

    };

    class ema
    {
    private:
        mdtx::online::updater::ema_updater<MDTX_INDICATOR_KAHAN> mx;

        double insert(double x)
        {
            mx.insert(x);
            return mx.m;
        }

    public:
        explicit ema(int period)
            : mx(2.0 / (period + 1))
        {
        }

        template <class InputIt, class OutputIt>
        void operator()(InputIt first, InputIt last, OutputIt output)
        {
            while (first != last)
            {
                *output++ = insert(*first++);
            }
        }

        void operator()(double input, double &output)
        {
            output = insert(input);
        }
    };

    class wma
    {
    private:
        boost::circular_buffer<double> xbuf;
        mdtx::online::updater::wma_updater<MDTX_INDICATOR_KAHAN> mx;

        double insert(double x)
        {
            if (xbuf.full())
            {
                mx.roll(x, xbuf.back());
            }
            else
            {
                mx.insert(x);
            }
            xbuf.push_front(x);
            return mx.wsum / mx.w;
        }

    public:
        friend class hma;

        explicit wma(int period)
            : xbuf(period),
              mx()
        {
        }

        template <class InputIt, class OutputIt>
        void operator()(InputIt first, InputIt last, OutputIt output)
        {
            while (first != last)
            {
                *output++ = insert(*first++);
            }
        }

        void operator()(double input, double &output)
        {
            output = insert(input);
        }
    };

    class wilders
    {
    private:
        mdtx::online::updater::ema_updater<MDTX_INDICATOR_KAHAN> mx;

        double insert(double x)
        {
            mx.insert(x);
            return mx.m;
        }

    public:
        explicit wilders(int period)
            : mx(1.0 / period)
        {
        }

        template <class InputIt, class OutputIt>
        void operator()(InputIt first, InputIt last, OutputIt output)
        {
            while (first != last)
            {
                *output++ = insert(*first++);
            }
        }

        void operator()(double input, double &output)
        {
            output = insert(input);
        }
    };

    // MA osc

    class smosc
    {
    private:
        boost::circular_buffer<double> fast_buf, slow_buf;
        mdtx::online::updater::sma_updater<MDTX_INDICATOR_KAHAN> fast_ma, slow_ma;

        double insert(double x)
        {
            if (slow_buf.full())
            {
                fast_ma.roll(x, fast_buf.back());
                slow_ma.roll(x, slow_buf.back());
            }
            else if (fast_buf.full())
            {
                fast_ma.roll(x, fast_buf.back());
                slow_ma.insert(x);
            }
            else
            {
                fast_ma.insert(x);
                slow_ma.insert(x);
            }
            fast_buf.push_back(x);
            slow_buf.push_back(x);
            return (fast_ma.m - slow_ma.m) / slow_ma.m;
        }

    public:
        smosc(int fast_period, int slow_period)
            : fast_buf(std::min(fast_period, slow_period)),
              slow_buf(std::max(fast_period, slow_period)),
              fast_ma(),
              slow_ma()
        {
        }

        template <class InputIt, class OutputIt>
        void operator()(InputIt first, InputIt last, OutputIt output)
        {
            while (first != last)
            {
                *output++ = insert(*first++);
            }
        }

        void operator()(double input, double &output)
        {
            output = insert(input);
        }
    };

    class emosc
    {
    private:
        mdtx::online::updater::ema_updater<MDTX_INDICATOR_KAHAN> fast_ma, slow_ma;

        double insert(double x)
        {
            fast_ma.insert(x);
            slow_ma.insert(x);
            return (fast_ma.m - slow_ma.m) / slow_ma.m;
        }

    public:
        emosc(int fast_period, int slow_period)
            : fast_ma(2.0 / (std::min(fast_period, slow_period) + 1)),
              slow_ma(2.0 / (std::max(fast_period, slow_period) + 1))
        {
        }

        template <class InputIt, class OutputIt>
        void operator()(InputIt first, InputIt last, OutputIt output)
        {
            while (first != last)
            {
                *output++ = insert(*first++);
            }
        }

        void operator()(double input, double &output)
        {
            output = insert(input);
        }
    };

    // extended MA

    class zlema
    {
    private:
        boost::circular_buffer<double> xbuf;
        mdtx::online::updater::ema_updater<MDTX_INDICATOR_KAHAN> mdt;

        double insert(double x)
        {
            double datum;
            if (xbuf.full())
            {
                datum = x + (x - xbuf.back());
            }
            else
            {
                datum = x;
            }
            xbuf.push_front(x);
            mdt.insert(datum);
            return mdt.m;
        }

    public:
        explicit zlema(int period)
            : xbuf(period / 2),
              mdt(2.0 / (period - 1))
        {
        }

        template <class InputIt, class OutputIt>
        void operator()(InputIt first, InputIt last, OutputIt output)
        {
            while (first != last)
            {
                *output++ = insert(*first++);
            }
        }

        void operator()(double input, double &output)
        {
            output = insert(input);
        }
    };

    class vwma
    {
    private:
        boost::circular_buffer<double> pbuf, vbuf;
        mdtx::online::updater::wsma_updater<MDTX_INDICATOR_KAHAN> mpv;

        double insert(double p, double v)
        {
            mpv.insert(p, v);
            if (pbuf.full())
            {
                mpv.remove(pbuf.back(), vbuf.back());
            }
            pbuf.push_front(p);
            vbuf.push_front(v);
            return mpv.m;
        }

    public:
        explicit vwma(int period)
            : pbuf(period),
              vbuf(period),
              mpv()
        {
        }

        template <class InputIt, class OutputIt>
        void operator()(InputIt price_first, InputIt price_last, InputIt volume, OutputIt output)
        {
            while (price_first != price_last)
            {
                *output++ = insert(*price_first++, *volume++);
            }
        }

        void operator()(double price, double volume, double &output)
        {
            output = insert(price, volume);
        }
    };

    class dema
    {
    private:
        mdtx::online::updater::xema_updater<2, MDTX_INDICATOR_KAHAN> mx;

        double insert(double x)
        {
            mx.insert(x);
            return 2.0 * mx.m[0] - mx.m[1];
        }

    public:
        explicit dema(int period)
            : mx(2.0 / (period + 1))
        {
        }

        template <class InputIt, class OutputIt>
        void operator()(InputIt first, InputIt last, OutputIt output)
        {
            while (first != last)
            {
                *output++ = insert(*first++);
            }
        }

        void operator()(double input, double &output)
        {
            output = insert(input);
        }
    };

    class tema
    {
    private:
        mdtx::online::updater::xema_updater<3, MDTX_INDICATOR_KAHAN> mx;

        double insert(double x)
        {
            mx.insert(x);
            return 3.0 * mx.m[0] - 3.0 * mx.m[1] + mx.m[2];
        }

    public:
        explicit tema(int period)
            : mx(2.0 / (period + 1))
        {
        }

        template <class InputIt, class OutputIt>
        void operator()(InputIt first, InputIt last, OutputIt output)
        {
            while (first != last)
            {
                *output++ = insert(*first++);
            }
        }

        void operator()(double input, double &output)
        {
            output = insert(input);
        }
    };

    // niche and wierd MA

    class vidya
    {
    private:
        double fa, ma;
        boost::circular_buffer<double> fast_buf, slow_buf;
        mdtx::online::updater::s2_updater<MDTX_INDICATOR_KAHAN> fast_s2, slow_s2;

        double insert(double x)
        {
            fast_s2.insert(x);
            slow_s2.insert(x);
            if (slow_buf.full())
            {
                fast_s2.remove(fast_buf.back());
                slow_s2.remove(slow_buf.back());
            }
            else if (fast_buf.full())
            {
                fast_s2.remove(fast_buf.back());
            }
            if (fast_buf.empty())
            {
                ma = x;
            }
            else
            {
                const double a = fa * std::sqrt(fast_s2.s2 / slow_s2.s2);
                ma += a * (x - ma);
            }
            fast_buf.push_front(x);
            slow_buf.push_front(x);
            return ma;
        }

    public:
        vidya(int fast_period, int slow_period, double fa)
            : fa(fa),
              ma(0.0),
              fast_buf(std::min(fast_period, slow_period)),
              slow_buf(std::max(fast_period, slow_period)),
              fast_s2(),
              slow_s2()
        {
        }

        template <class InputIt, class OutputIt>
        void operator()(InputIt first, InputIt last, OutputIt output)
        {
            while (first != last)
            {
                *output++ = insert(*first++);
            }
        }

        void operator()(double input, double &output)
        {
            output = insert(input);
        }
    };

    class kama
    {
    private:
        bool init;
        boost::circular_buffer<double> xbuf, dbuf;
        mdtx::online::updater::sum_updater<MDTX_INDICATOR_KAHAN> dsum;
        double mx, f, s, fs;

        double insert(double x)
        {
            double d1, dn, e, a;
            if (init)
            {
                d1 = std::abs(x - xbuf.front());
                if (dbuf.full())
                {
                    dsum.roll(d1, dbuf.back());
                    dn = x - xbuf.back();
                    e = dn / dsum.s;
                    a = std::pow(e * fs + s, 2);
                    mx += a * (x - mx);
                }
                else
                {
                    dsum.insert(d1);
                    mx = x;
                }
                dbuf.push_front(d1);
            }
            else
            {
                init = true;
                mx = x;
            }
            xbuf.push_front(x);
            return mx;
        }

    public:
        kama(int period, int fast_period = 2, int slow_period = 30)
            : init(false),
              xbuf(period),
              dbuf(period),
              dsum(),
              mx(0.0),
              f(2.0 / (std::min(fast_period, slow_period) + 1)),
              s(2.0 / (std::max(fast_period, slow_period) + 1)),
              fs(std::abs(f - s))
        {
        }

        template <class InputIt, class OutputIt>
        void operator()(InputIt first, InputIt last, OutputIt output)
        {
            while (first != last)
            {
                *output++ = insert(*first++);
            }
        }

        void operator()(double input, double &output)
        {
            output = insert(input);
        }
    };

    class hma
    {
    private:
        wma wma_m, wma_n, wma_s;

        double insert(double x)
        {
            double mm = wma_m.insert(x);
            double mn = wma_n.insert(x);
            double dd = mm * 2.0 - mn;
            return wma_s.insert(dd);
        }

    public:
        explicit hma(int period)
            : wma_m(period / 2),
              wma_n(static_cast<int>(std::sqrt(period))),
              wma_s(period)
        {
        }

        template <class InputIt, class OutputIt>
        void operator()(InputIt first, InputIt last, OutputIt output)
        {
            while (first != last)
            {
                *output++ = insert(*first++);
            }
        }

        void operator()(double input, double &output)
        {
            output = insert(input);
        }
    };

    // stats

    class mae
    {
    private:
        boost::circular_buffer<double> xbuf;
        mdtx::online::updater::sma_updater<MDTX_INDICATOR_KAHAN> mx;

        double insert(double x)
        {
            if (xbuf.full())
            {
                mx.roll(x, xbuf.back());
            }
            else
            {
                mx.insert(x);
            }
            xbuf.push_front(x);
            double e = 0.0;
            for (double xx : xbuf)
            {
                e += std::abs(xx - mx.m);
            }
            e /= xbuf.size();
            return e;
        }

    public:
        explicit mae(int period)
            : xbuf(period),
              mx()
        {
        }

        template <class InputIt, class OutputIt>
        void operator()(InputIt first, InputIt last, OutputIt output)
        {
            while (first != last)
            {
                *output++ = insert(*first++);
            }
        }

        void operator()(double input, double &output)
        {
            output = insert(input);
        }
    };

    // Not really MA, but actually MA

    class macd
    {
    private:
        int n, m, t;
        mdtx::online::updater::ema_updater<MDTX_INDICATOR_KAHAN> ema_n, ema_m, ema_macd;
        double _macd, _sig, _hist;

        void insert(double x)
        {
            ema_n.insert(x);
            ema_m.insert(x);
            if (t == m)
            {
                _macd = ema_n.m - ema_m.m;
                ema_macd.insert(_macd);
                _sig = ema_macd.m;
                _hist = _macd - _sig;
            }
            else
            {
                t += 1;
            }
        }

    public:
        macd(int short_period = 12, int long_period = 26, int sig_period = 9)
            : n(std::min(short_period, long_period)),
              m(std::max(short_period, long_period)),
              ema_n(2.0 / (n + 1)),
              ema_m(2.0 / (m + 1)),
              ema_macd(2.0 / (sig_period + 1)),
              _macd(0.0),
              _sig(0.0),
              _hist(0.0)
        {
        }

        template <class InputIt, class OutputIt>
        void operator()(InputIt first, InputIt last, OutputIt output_macd, OutputIt output_sig, OutputIt output_hist)
        {
            while (first != last)
            {
                insert(*first++);
                *output_macd++ = _macd;
                *output_sig++ = _sig;
                *output_hist++ = _hist;
            }
        }

        void operator()(double input, double &output_macd, double &output_sig, double &output_hist)
        {
            insert(input);
            output_macd = _macd;
            output_sig = _sig;
            output_hist = _hist;
        }
    };

    class rsi
    {
    private:
        bool init;
        double alpha, last, ema_up, ema_down;

        double insert(double x)
        {
            double up = 0.0;
            double down = 0.0;
            if (init)
            {
                if (x > last)
                {
                    up = x - last;
                }
                else
                {
                    down = last - x;
                }
                ema_up += alpha * (up - ema_up);
                ema_down += alpha * (down - ema_down);
            }
            else
            {
                init = true;
            }
            last = x;
            return 100.0 / (1.0 + ema_up / ema_down);
        }

    public:
        explicit rsi(int period)
            : init(false),
              alpha(1.0 / period),
              last(0.0),
              ema_up(MDTX_INDICATOR_EPS),
              ema_down(MDTX_INDICATOR_EPS)
        {
        }

        template <class InputIt, class OutputIt>
        void operator()(InputIt first, InputIt last, OutputIt output)
        {
            while (first != last)
            {
                *output++ = insert(*first++);
            }
        }

        void operator()(double input, double &output)
        {
            output = insert(input);
        }
    };

    class cci
    {
    private:
        boost::circular_buffer<double> xbuf;
        mdtx::online::updater::sma_updater<MDTX_INDICATOR_KAHAN> mx;

        double insert(double x)
        {
            if (xbuf.full())
            {
                mx.roll(x, xbuf.back());
            }
            else
            {
                mx.insert(x);
            }
            xbuf.push_front(x);
            // MAE: O(N)
            double md = 0.0;
            for (auto xx : xbuf)
            {
                md += std::abs(xx - mx.m);
            }
            md /= xbuf.size();
            return (x - mx.m) / (0.015 * md);
        }

    public:
        explicit cci(int period)
            : xbuf(period),
              mx()
        {
        }

        template <class InputIt, class OutputIt>
        void operator()(InputIt first, InputIt last, OutputIt output)
        {
            while (first != last)
            {
                *output++ = insert(*first++);
            }
        }

        void operator()(double input, double &output)
        {
            output = insert(input);
        }
    };

    class dpo
    {
    private:
        int mid;
        boost::circular_buffer<double> xbuf;
        mdtx::online::updater::sma_updater<MDTX_INDICATOR_KAHAN> mx;

        double insert(double x)
        {
            double dmid;
            if (xbuf.full())
            {
                mx.roll(x, xbuf.back());
                dmid = xbuf[mid] - mx.m;
            }
            else
            {
                mx.insert(x);
                dmid = 0.0;
            }
            xbuf.push_front(x);
            return dmid;
        }

    public:
        explicit dpo(int period)
            : mid(period / 2),
              xbuf(period),
              mx()
        {
        }

        template <class InputIt, class OutputIt>
        void operator()(InputIt first, InputIt last, OutputIt output)
        {
            while (first != last)
            {
                *output++ = insert(*first++);
            }
        }

        void operator()(double input, double &output)
        {
            output = insert(input);
        }
    };

    class mass
    {
    private:
        boost::circular_buffer<double> ebuf;
        mdtx::online::updater::sum_updater<MDTX_INDICATOR_KAHAN> esum;
        mdtx::online::updater::xema_updater<2, MDTX_INDICATOR_KAHAN> mx;

        double insert(double x)
        {
            mx.insert(x);
            double e = mx.m[0] / mx.m[1];
            if (ebuf.full())
            {
                esum.roll(e, ebuf.back());
            }
            else
            {
                esum.insert(e);
            }
            ebuf.push_front(e);
            return esum.s;
        }

    public:
        explicit mass(int period)
            : ebuf(period),
              esum(),
              mx(2.0 / (period + 1))
        {
        }

        template <class InputIt, class OutputIt>
        void operator()(InputIt first, InputIt last, OutputIt output)
        {
            while (first != last)
            {
                *output++ = insert(*first++);
            }
        }

        void operator()(double input, double &output)
        {
            output = insert(input);
        }
    };

    class trix
    {
    private:
        mdtx::online::updater::xema_updater<3, MDTX_INDICATOR_KAHAN> mx;
        double last;

        double insert(double x)
        {
            double val = 0.0;
            if (mx.init)
            {
                mx.insert(x);
                val = (mx.m[2] - last) / mx.m[2];
                last = mx.m[2];
            }
            else
            {
                mx.insert(x);
                last = mx.m[2];
            }
            return val;
        }

    public:
        explicit trix(int period)
            : mx(2.0 / (period + 1))
        {
        }

        template <class InputIt, class OutputIt>
        void operator()(InputIt first, InputIt last, OutputIt output)
        {
            while (first != last)
            {
                *output++ = insert(*first++);
            }
        }

        void operator()(double input, double &output)
        {
            output = insert(input);
        }
    };

    class atr
    {
    private:
        bool init;
        mdtx::online::updater::ema_updater<MDTX_INDICATOR_KAHAN> ma;
        double last;

        double insert(double high, double low, double close)
        {
            double tr;
            if (init)
            {
                tr = std::max(std::max(high - low, std::abs(high - last)), std::abs(low - last));
            }
            else
            {
                tr = high - low;
                init = true;
            }
            last = close;
            ma.insert(tr);
            return ma.m;
        }

    public:
        explicit atr(int period)
            : init(false),
              ma(1.0 / period)
        {
        }

        template <class InputIt, class OutputIt>
        void operator()(InputIt high, InputIt high_last, InputIt low, InputIt close, OutputIt output)
        {
            while (high != high_last)
            {
                *output++ = insert(*high++, *low++, *close++);
            }
        }

        void operator()(double high, double low, double close, double &output)
        {
            output = insert(high, low, close);
        }
    };

}
